<?PHP
/*!
 *
 * @Application : GetIP.class.php
 * @Description : IP地址数据检索
 * @Encode : 65001 Unicode ( UTF-8 NO BOM )
 * @Indent : Indent with 4 spaces.
 * @Author : HK <hkyyang@gmail.com>
 */
class GetIP {

    // 文件句柄
    private $Handle = NULL;
    private $First = NULL;
    private $Last = NULL;
    // 数据总数
    private $Count = 0;
    private $BinFile = NULL;
    // 当前客户端IP
    private $ClientIP = 0;

    /*!
     * 构造方法
     *
     * @access public
     * @param $
     * @return Void
     */
    public function __construct() {
        $BinFile = realpath( dirname( __FILE__ ) . DS .  __CLASS__ . '.bin' );
        if( ! is_file( $BinFile ) ) {
            throw_exception( 'Bin file not exist' );
        }
        $this -> BinFile = $BinFile;
        $this -> ClientIP = get_ip();
        $this -> Handle = fopen( $this -> BinFile, 'rb' );
        $this -> First = $this -> getFourByteLong();
        $this -> Last = $this -> getFourByteLong();
        $this -> Count = ( $this -> Last - $this -> First ) / 7;
    }

    /*!
     * 检索IP地址的详细信息
     *
     * @access public
     * @param String : $ip
     * @param Boolean : $detailed, 返回详细信息数组
     * @return Array | String
     */
    public function search( $ip = NULL, $detailed = FALSE ) {
        if( is_null( $ip ) ) {
            $ip = $this -> ClientIP;
        }
        // 域名转换为IP地址
        $ip = gethostbyname( $ip );
        $address = array( 'ip' => $ip, 0 => 'N/A', 1 => '' );
        $offset = $this -> getOffset( $ip );
        if( $offset == 0 ) {
            return $address;
        }
        // 指针定位到索引区
        fseek( $this -> Handle, $offset );
        // 索引区域内开始IP地址
        $address[ 'start_ip' ] = long2ip( $this -> getFourByteLong() );
        // 索引区IP对应的偏移地址
        $start_offset = $this -> getRedirectOffset();
        // 指针定位到数据区
        fseek( $this -> Handle, $start_offset );
        // 结束IP地址
        $address[ 'end_ip' ] = long2ip( $this -> getFourByteLong() );
        // 读取标记字节
        $flag = $this -> getFlag();
        switch( ord( $flag ) ) {
            // 地区1、2都重定向
            case 1 :
                $start_offset = $this -> getRedirectOffset();
                fseek( $this -> Handle, $start_offset );
                $flag = $this -> getFlag();
                switch( ord( $flag ) ) :
                    case 2 :
                        fseek( $this -> Handle, $this -> getRedirectOffset() );
                        $address[ 0 ] = $this -> getString();
                        fseek( $this -> Handle, $start_offset + 4 );
                        $address[ 1 ] = $this -> getAddress();
                    break;
                    default :
                        fseek( $this -> Handle, $start_offset );
                        $address[ 0 ] = $this -> getString();
                        $address[ 1 ] = $this -> getAddress();
                    break;
                endswitch;
            break;
            // 地区1重定向, 地区2没有重定向
            case 2 :
                $redirect_offset = $this -> getRedirectOffset();
                fseek( $this -> Handle, $redirect_offset );
                $address[ 0 ] = $this -> getString();
                fseek( $this -> Handle, $start_offset + 8 );
                $address[ 1 ] = $this -> getAddress();
            break;
            //地区1、2都没有重定向
            default:
                fseek( $this -> Handle, $start_offset + 4 );
                $address[ 0 ] = $this -> getString();
                $address[ 1 ] = $this -> getAddress();
            break;
        }
        if( stripos( $address[ 0 ], 'CZ88.NET' ) ) {
            $address[ 0 ] = 'N/A';
        }
        if( stripos( $address[ 1 ], 'CZ88.NET' ) ) {
            $address[ 1 ] = '';
        }
        // 编码转换
        $address[ 0 ] = iconv( 'GB18030', 'UTF-8', $address[ 0 ] );
        $address[ 1 ] = iconv( 'GB18030', 'UTF-8', $address[ 1 ] );
        // 返回详细信息数组
        if( $detailed ) {
            return $address;
        }
        return $address[ 0 ] . ' ' . $address[ 1 ];
    }

    /*!
     * 获取字符串信息
     *
     * @access private
     * @param $
     * @return String
     */
    private function getString() {
        $split = fread( $this -> Handle, 1 );
        $string = '';
        while( ord( $split ) != 0 ) {
            $string .= $split;
            $split = fread( $this -> Handle, 1 );
        }
        return $string;
    }

    /*!
     * 获取地址信息
     *
     * @access private
     * @param $
     * @return String
     */
    private function getAddress() {
        $pointer = ftell( $this -> Handle );
        $flag = $this -> getFlag();
        switch( ord( $flag ) ) {
            case 0 :
                $address = '';
            break;
            case 1 :
            case 2 :
                fseek( $this -> Handle, $this -> getRedirectOffset() );
                $address = $this -> getString();
            break;
            default :
                fseek( $this -> Handle, $pointer );
                $address = $this -> getString();
            break;
        }
        return $address;
    }

    /*!
     * 读取4个字节并解包长整形
     *
     * @access private
     * @param $
     * @return Integer
     */
    private function getFourByteLong() {
        // Format: V - unsigned long (always 32 bit, little endian byte order)
        return current( ( unpack( 'V', fread( $this -> Handle, 4 ) ) ) );
    }

    /*!
     * 将IP字符串转换为IPv4再打包
     *
     * @access private
     * @param String : $ip
     * @return Integer
     */
    private function ipPackage( $ip ) {
        // Format: N - unsigned long (always 32 bit, big endian byte order)
        return pack( 'N', intval( ip2long( $ip ) ) );
    }

    /*!
     * 读取重定向的偏移地址
     *
     * @access private
     * @param String : $ip
     * @return Integer
     */
    private function getRedirectOffset() {
        // Format: V - unsigned long (always 32 bit, little endian byte order)
        return current( ( unpack( 'V', fread( $this -> Handle, 3 ) . chr( 0 ) ) ) );
    }

    /*!
     * 读取标记字节
     *
     * @access private
     * @param $
     * @return Integer
     */
    private function getFlag() {
        return fread( $this -> Handle, 1 );
    }

    /*!
     * 检索IP地址索引偏移地址
     *
     * @access private
     * @param String : $ip
     * @return Integer
     */
    private function getOffset( $ip ) {
        $ip = $this -> ipPackage( $ip );
        $Low = 0;
        $High = $this -> Count;
        // 二分查找法
        while( $Low <= $High ) {
            // 获取中间位置
            $Mid = floor( ( $Low + $High ) / 2 );
            // 指针定位
            fseek( $this -> Handle, $this -> First + $Mid * 7 );
            // 读取索引区内当前IP地址, 并将little-endian字节序转换为big-endian字节序
            $current = strrev( fread( $this -> Handle, 4 ) );
            if( $ip < $current ) {
                $High = $Mid - 1;
            } else {
                fseek( $this -> Handle, $this -> getRedirectOffset() );
                $end = strrev( fread( $this -> Handle, 4 ) );
                if( $ip > $end ) {
                    $Low = $Mid + 1;
                } else {
                    return $this -> First + $Mid * 7;
                }
            }
        }
        return 0;
    }

    /*!
     * 析构方法
     *
     * @access public
     * @param $
     * @return Void
     */
    public function __destruct() {
        if( is_resource( $this -> Handle ) ) {
            fclose( $this -> Handle );
        }
    }

    /*!
     * 当前客户端 IP
     *
     * @access public
     * @param $
     * @return String
     */
    public function __toString() {
        return $this -> ClientIP;
    }

}

?>